﻿// Date: 14.07.11

// Dependencies: Some parts of the properties are similar to the ones of the GoblinXNA Camera-class

// Revision 9

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//using GoblinXNA.SceneGraph;
using Microsoft.Xna.Framework;
//using GoblinXNA;
using Microsoft.Xna.Framework.Input;
using NOVA.Utilities;

namespace NOVA.Scenery
{
    /// <summary>
    /// Creates a camera object for the scene.
    /// </summary>
    public class CameraObject : ITransformable
    {
        #region Members
        protected bool m_bTargetSet;
        protected BoundingFrustum m_viewFrustum;
        protected int m_iID;

        protected ITransformable m_chaseObject;
        internal Vector3 m_lastChaseObjectPosition;
        protected Vector3 m_chaseDistance;

        protected Vector3 m_position;
        protected Quaternion m_rotation;
        protected Matrix m_view;
        protected Matrix m_projection;
        protected Matrix m_cameraTransformation;
        protected float m_fieldOfView;
        protected float m_aspectRatio;
        protected float m_zNearPlane;
        protected float m_zFarPlane;
        protected Vector3 m_targetPos;
        protected Vector3 m_upVector;
        protected Vector3 m_angles;
        protected string m_strName;
        protected string m_strType;

        protected bool m_bModifyView;
        protected bool m_bModifyProjection;

        protected float interpupillaryDistance;
        protected float interpupillaryShift;
        protected float focalLength;
        
        protected Matrix leftViewMatrix;
        protected Matrix rightViewMatrix;
        protected Matrix leftProjectionMatrix;
        protected Matrix rightProjectionMatrix;

        protected bool modifyLeftView;
        protected bool modifyRightView;
        protected bool modifyLeftProjection;
        protected bool modifyRightProjection;

        protected Matrix m_tmpMat1;
        protected Matrix m_tmpMat2;
        private bool enable;
        private bool enabled;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the ID of the controller.
        /// </summary>
        public int ID
        {
            get { return m_iID; }
        }

        /// <summary>
        /// Gets or sets the scene object, that should be followed by this camera.
        /// </summary>
        public ITransformable ChaseObject
        {
            get { return m_chaseObject; }
            set 
            { 
                m_chaseObject = value;

                m_lastChaseObjectPosition = value.Position;
            }

        }

        /// <summary>
        /// Gets the current euler angles.
        /// </summary>
        public Vector3 Angles
        {
            get { return m_angles; }
            // TODO: set
        }

        /// <summary>
        /// Gets or sets the current position of the camera.
        /// </summary>
        public Vector3 Position
        {
            get { return m_position; }
            set
            {
                m_position = value;

                m_bModifyView = true;
                modifyLeftView = true;
                modifyRightView = true;

                //Matrix.CreateFromQuaternion(ref m_rotation, out m_tmpMat1);
                //Matrix.CreateTranslation(ref m_position, out m_tmpMat2);
                //Matrix.Multiply(ref m_tmpMat1, ref m_tmpMat2, out m_cameraTransformation);

                //Matrix.CreateTranslation(ref m_position, out m_tmpMat1);
                //Matrix.Multiply(ref m_cameraTransformation, ref m_tmpMat1, out m_cameraTransformation);
                
                m_cameraTransformation.Translation = value;
            }
        }

        /// <summary>
        /// Gets or sets the current orientation of the camera.
        /// </summary>
        public Quaternion Orientation
        {
            get { return m_rotation; }
            set
            {
                m_rotation = value;

                m_bModifyView = true;
                modifyLeftView = true;
                modifyRightView = true;

                Matrix.CreateFromQuaternion(ref m_rotation, out m_tmpMat1);
                Matrix.CreateTranslation(ref m_position, out m_tmpMat2);
                Matrix.Multiply(ref m_tmpMat1, ref m_tmpMat2, out m_cameraTransformation);

                m_targetPos = Vector3.Transform(Vector3.Forward, m_cameraTransformation);
            }
        }

        /// <summary>
        /// Gets the current bounding box of the camera.
        /// </summary>
        public BoundingBox BoundingBox
        {
            get { return new BoundingBox(Position - Vector3.One * 0.5f, Position + Vector3.One * 0.5f); }
        }
        
        /// <summary>
        /// Not used for the camrea.
        /// </summary>
        public Vector3 Scale
        {
            get
            {
                return Vector3.Zero; // TODO
            }
            set
            {
                // TODO
            }
        }

        /// <summary>
        /// If enabled, the camera can be used
        /// </summary>
        public bool Enabled 
        {
            get { return this.enabled; }
            set { this.enable = value; }
        }

        /// <summary>
        /// Gets the current name of the camera.
        /// </summary>
        public string Name 
        {
            get { return m_strName; }
            set { m_strName = value; } 
        }


        public string Type
        {
            get { return m_strType; }
            set { m_strType = value; }
        }

        /// <summary>
        /// Gets or sets the current view frustum of the camera.
        /// </summary>
        public BoundingFrustum ViewFrustum
        {
            get { return m_viewFrustum; }
            set { m_viewFrustum = value; }
        }

        /// <summary>
        /// Gets the current view matrix of the camera.
        /// </summary>
        public Matrix ViewMatrix
        {
            get
            {
                if (m_bModifyView)
                {
                    ////Matrix cameraRotation = Matrix.CreateRotationX(m_angles.X) * Matrix.CreateRotationY(m_angles.Y);
                    ////Vector3 targetPos = m_position + Vector3.Transform(Vector3.Forward, m_cameraTransformation);
                    ////m_targetPos = targetPos;

                    //Vector3 upVector = Vector3.Transform(Vector3.Up, m_cameraTransformation);
                    //m_upVector = upVector;

                    //m_view = Matrix.CreateLookAt(m_position, m_targetPos, upVector);

                    //m_bModifyView = false;

                    Vector3 location = m_position;

                    Vector3 target = -Vector3.UnitZ;
                    //if (!m_bTargetSet)
                    //{                        
                    //    Vector3.Transform(ref target, ref m_cameraTransformation, out target);
                    //    m_targetPos = target;
                    //}
                    //else
                    //{
                        target = m_targetPos;
                        m_bTargetSet = false;
                    //}

                    Vector3 up = Vector3.UnitY;
                    Vector3.Transform(ref up, ref m_cameraTransformation, out up);
                    m_upVector = up;

                    Vector3.Subtract(ref up, ref location, out up);
                    Matrix.CreateLookAt(ref m_position/* location*/, ref target, ref up, out m_view);

                    m_angles = Helpers.AngleTo(m_position, m_targetPos);

                    m_bModifyView = false;
                }

                return m_view; 
            }
            set 
            { 
                m_view = value;
                m_bModifyView = false;            
            }
        }

        /// <summary>
        /// Gets the current projection matrix of the camera.
        /// </summary>
        public Matrix ProjectionMatrix
        {
            get 
            {
                if (m_bModifyProjection)
                {
                    Matrix.CreatePerspectiveFieldOfView(m_fieldOfView, m_aspectRatio, m_zNearPlane, m_zFarPlane, out m_projection);

                    m_bModifyProjection = false;
                }
 
                return m_projection; 
            }
            set 
            { 
                m_projection = value;
                m_bModifyProjection = false;
            }
        }

        /// <summary>
        /// Gets or sets the vertical field of view in radians. 
        /// The default value is MathHelper.ToRadians(45)
        /// </summary>
        public virtual float FieldOfViewY
        {
            get { return m_fieldOfView; }
            set
            {
                m_fieldOfView = value;
                m_bModifyProjection = true;
            }
        }

        /// <summary>
        /// Gets or sets the aspect ratio. Default value is set to Viewport.Width / Viewport.Height
        /// </summary>
        public virtual float AspectRatio
        {
            get { return m_aspectRatio; }
            set
            {
                m_aspectRatio = value;
                m_bModifyProjection = true;
            }
        }

        /// <summary>
        /// Gets or sets the distance of the near clipping plane from the view position.
        /// The default value is 0.01f.
        /// </summary>
        public virtual float ZNearPlane
        {
            get { return m_zNearPlane; }
            set
            {
                m_zNearPlane = value;
                m_bModifyProjection = true;
            }
        }

        /// <summary>
        /// Gets or sets the distance of the far clipping plane from the view position.
        /// The default value is 1000.0f;
        /// </summary>
        public virtual float ZFarPlane
        {
            get { return m_zFarPlane; }
            set
            {
                m_zFarPlane = value;
                m_bModifyProjection = true;
            }
        }

        public Matrix ControllerMatrix
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public bool HasController
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Gets the transformation created by composing Rotation and Translation.
        /// </summary>
        /// <remarks>
        /// = Matrix.CreateFromQuaternion(rotation) * Matrix.CreateTranslation(translation)
        /// </remarks>
        /// <see cref="Translation"/>
        /// <seealso cref="Rotation"/>
        public virtual Matrix CameraTransformation
        {
            get { return m_cameraTransformation; }
            set 
            { 
                m_cameraTransformation = value;

                m_bModifyView = true; 
            }
        }

        /// <summary>
        /// Gets or sets the current target of the camera.
        /// </summary>
        public Vector3 Target
        {
            get { return m_targetPos; }
            set 
            { 
                m_targetPos = value; 
                m_bModifyView = true;
                m_bTargetSet = true;

                Vector3 angles = Helpers.AngleTo(m_position, value);
                m_cameraTransformation = Matrix.CreateFromYawPitchRoll(angles.Y, angles.X, angles.Z);
            }
        }

        /// <summary>
        /// ﻿Gets or sets the distance between the two eyes, measured at the pupils.
        /// </summary>
        public virtual float InterpupillaryDistance
        {
            get { return interpupillaryDistance; }
            set
            {
                interpupillaryDistance = value;
                ModifyAll();
            }
        }

        /// <summary>
        /// Gets or sets the shift of the interpupillary distance from the center of the
        /// two eyes. Positive value will shift toward the right eye, and negative value
        /// will shift toward the left eye. The default value is 0.
        /// </summary>
        /// <remarks>
        /// This property is used when center of the stereo display is not in the middle
        /// of the two eyes.
        /// </remarks>
        public virtual float InterpupillaryShift
        {
            get { return interpupillaryShift; }
            set
            {
                interpupillaryShift = value;
                ModifyAll();
            }
        }

        /// <summary>
        /// Gets or sets the focal length from the camera/eye.
        /// </summary>
        public virtual float FocalLength
        {
            get { return focalLength; }
            set
            {
                focalLength = value;
                ModifyAll();
            }
        }

        /// <summary>
        /// Gets or sets the view matrix of the left eye.
        /// </summary>
        public virtual Matrix LeftView
        {
            get
            {
                if (modifyLeftView)
                {
                    //Vector3 rightVec = Vector3.UnitX;
                    //Vector3 upVec = Vector3.UnitY;
                    //Vector3 target = -Vector3.UnitZ;

                    //Matrix rotMat = Matrix.CreateFromQuaternion(m_rotation);
                    //Vector3 camPos = m_position + (rightVec * (-interpupillaryDistance + interpupillaryShift));

                    //Vector3.Transform(ref target, ref rotMat, out target);
                    //Vector3.Transform(ref upVec, ref rotMat, out upVec);
                    //target += camPos;

                    //Matrix.CreateLookAt(ref camPos, ref m_targetPos, ref upVec, out leftViewMatrix);
                    
                    Vector3 location = m_position + (Vector3.UnitX * (interpupillaryDistance + interpupillaryShift)); ;

                    Matrix cameraTransformation = Matrix.Identity;

                    Matrix.CreateFromQuaternion(ref m_rotation, out m_tmpMat1);
                    Matrix.CreateTranslation(ref location, out m_tmpMat2);
                    Matrix.Multiply(ref m_tmpMat1, ref m_tmpMat2, out cameraTransformation);

                    Matrix.CreateFromQuaternion(ref m_rotation, out m_tmpMat1);
                    Matrix.CreateTranslation(ref m_position, out m_tmpMat2);
                    Matrix.Multiply(ref m_tmpMat1, ref m_tmpMat2, out m_cameraTransformation);
                    

                    Vector3 target = -Vector3.UnitZ;
                    Vector3.Transform(ref target, ref cameraTransformation, out target);
                    m_targetPos = target;// +location;

                    Vector3 up = Vector3.UnitY;
                    Vector3.Transform(ref up, ref cameraTransformation, out up);
                    //m_upVector = up;

                    Vector3.Subtract(ref up, ref location, out up);
                    Matrix.CreateLookAt(ref location, ref m_targetPos, ref up, out leftViewMatrix);

                    modifyLeftView = false;
                }

                return leftViewMatrix;
            }
            set
            {
                leftViewMatrix = value;
                modifyLeftView = false;
            }
        }

        /// <summary>
        /// Gets or sets the view matrix of the right eye.
        /// </summary>
        public virtual Matrix RightView
        {
            get
            {
                if (modifyRightView)
                {
                    //Vector3 rightVec = Vector3.UnitX;
                    //Vector3 upVec = Vector3.UnitY;
                    //Vector3 target = -Vector3.UnitZ;

                    //Matrix rotMat = Matrix.CreateFromQuaternion(m_rotation);
                    //Vector3 camPos = m_position + (rightVec * (interpupillaryDistance + interpupillaryShift));

                    //Vector3.Transform(ref target, ref rotMat, out target);
                    //Vector3.Transform(ref upVec, ref rotMat, out upVec);
                    //target += camPos;

                    //Matrix.CreateLookAt(ref camPos, ref m_targetPos, ref upVec, out rightViewMatrix);


                    Vector3 location = m_position + (Vector3.UnitX * (-interpupillaryDistance + interpupillaryShift));

                    Matrix cameraTransformation = Matrix.Identity;

                    Matrix.CreateFromQuaternion(ref m_rotation, out m_tmpMat1);
                    Matrix.CreateTranslation(ref location, out m_tmpMat2);
                    Matrix.Multiply(ref m_tmpMat1, ref m_tmpMat2, out cameraTransformation);
                    
                    
                    Vector3 target = -Vector3.UnitZ;

                    Vector3.Transform(ref target, ref cameraTransformation, out target);
                    m_targetPos = target;// +location;

                    Vector3 up = Vector3.UnitY;
                    Vector3.Transform(ref up, ref cameraTransformation, out up);
                    //m_upVector = up;

                    Vector3.Subtract(ref up, ref location, out up);
                    Matrix.CreateLookAt(ref location, ref m_targetPos, ref up, out rightViewMatrix);


                    modifyRightView = false;
                }

                return rightViewMatrix;
            }
            set
            {
                rightViewMatrix = value;
                modifyRightView = false;
            }
        }

        /// <summary>
        /// Gets or sets the projection matrix of the left eye.
        /// </summary>
        public virtual Matrix LeftProjection
        {
            get
            {
                if (modifyLeftProjection)
                {
                    float top = m_zNearPlane * (float)Math.Tan(MathHelper.ToDegrees(m_fieldOfView) / 2);
                    float right = m_aspectRatio * top;
                    float frustumshift = (interpupillaryDistance / 2) * m_zNearPlane / focalLength;
                    float left = -right - frustumshift;
                    right = right - frustumshift;

                    leftProjectionMatrix = Matrix.CreatePerspectiveOffCenter(left, right,
                        -top, top, m_zNearPlane, m_zFarPlane);

                    modifyLeftProjection = false;
                }

                return leftProjectionMatrix;
            }
            set
            {
                leftProjectionMatrix = value;
                modifyLeftProjection = false;
            }
        }

        /// <summary>
        /// Gets or sets the projection matrix of the right eye.
        /// </summary>
        public virtual Matrix RightProjection
        {
            get
            {
                if (modifyRightProjection)
                {
                    float top = m_zNearPlane * (float)Math.Tan(MathHelper.ToDegrees(m_fieldOfView) / 2);
                    float right = m_aspectRatio * top;
                    float frustumshift = (interpupillaryDistance / 2) * m_zNearPlane / focalLength;
                    float left = -right + frustumshift;
                    right = right + frustumshift;

                    rightProjectionMatrix = Matrix.CreatePerspectiveOffCenter(left, right,
                        -top, top, m_zNearPlane, m_zFarPlane);

                    modifyRightProjection = false;
                }

                return rightProjectionMatrix;
            }
            set
            {
                rightProjectionMatrix = value;
                modifyRightProjection = false;
            }
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Creates a camera object for the scene.
        /// </summary>
        public CameraObject(Vector3 pos, Vector3 target) : this(pos, target, 45, 0.1f, 1000f)
        {
        }

        /// <summary>
        /// Creates a camera object for the scene.
        /// </summary>
        public CameraObject(Vector3 position, Vector3 target, float FieldOfViewAngle, float nearPlane, float farPlane) : base()
        {
            m_position = position;
            m_rotation = Quaternion.Identity;
            m_view = Matrix.Identity;
            m_projection = Matrix.Identity;
            m_cameraTransformation = Matrix.Identity;

            Vector3 location = m_position;
            Vector3 up = Vector3.UnitY;

            m_angles = Helpers.AngleTo(position, target);

            m_cameraTransformation = Matrix.CreateRotationX(m_angles.X) * Matrix.CreateRotationY(m_angles.Y) * Matrix.CreateRotationZ(m_angles.Z);


            Vector3.Transform(ref up, ref m_cameraTransformation, out up);
            //Vector3.Subtract(ref up, ref location, out up);
            Matrix.CreateLookAt(ref location, ref target, ref up, out m_view);

            
            m_targetPos = target;

            m_iID = Core.GetNextNodeID();

            m_fieldOfView = MathHelper.ToRadians(FieldOfViewAngle);
            m_aspectRatio = Core.Device.Viewport.AspectRatio;
            m_zNearPlane = nearPlane;
            m_zFarPlane = farPlane;

            m_strType = "Camera";
            m_strName = "";
            
            Matrix.CreatePerspectiveFieldOfView(m_fieldOfView, m_aspectRatio, m_zNearPlane, m_zFarPlane, out m_projection);

            m_bModifyView = false;
            m_bModifyProjection = false;

            m_viewFrustum = new BoundingFrustum(m_view * m_projection);

            interpupillaryDistance = 1.0f;
            interpupillaryShift = 0;
            focalLength = 10;

            leftViewMatrix = Matrix.Identity;
            rightViewMatrix = Matrix.Identity;
            leftProjectionMatrix = Matrix.Identity;
            rightProjectionMatrix = Matrix.Identity;

            modifyLeftView = true;
            modifyRightView = true;
            modifyLeftProjection = true;
            modifyRightProjection = true;
        }
        #endregion

        /// <summary>
        /// Forces the recalculation of all matrices
        /// </summary>
        protected void ModifyAll()
        {
            modifyLeftView = true;
            modifyRightView = true;
            modifyLeftProjection = true;
            modifyRightProjection = true;
        }
    }
}
